package com.ams.auth.security.config;

import cn.hutool.json.JSONUtil;
import com.ams.auth.comm.enums.ClientEnums;
import com.ams.auth.security.details.MoreAuthenticatedUserDetailsService;
import com.ams.common.result.R;
import com.ams.common.result.ResultCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;

import java.util.HashMap;
import java.util.Map;

/**
 * @author： 乐哥聊编程(全平台同号)
 */
@Configuration
@EnableWebSecurity
@Slf4j
// 属性就不需要使用autoweird注入了
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService memberUserDetailsService;

    private final UserDetailsService sysUserDetailsService;
    private final String realName = ".";

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/oauth/**", "/test/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .csrf().disable()
                // 设置未认证处理
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint());
    }


    /**
     * 认证管理对象
     * <p>
     * 发现你实现了UserDetailsService接口就不会自动构建一个默认的AuthenticationManager。
     * <p>
     * 这时候开发者就必须自定义AuthenticationManagerBuilder或者主动暴露一个authenticationManagerBean，
     * 否则AuthenticationManager就会是null
     * <p>
     * 解决办法一:主动暴露一个默认的authenticationManagerBean
     * <p>
     * 方案二：在Security配置类（即extends了WebSecurityConfigurerAdapter的配置类）中注入自定义的UserDetailsService，
     * 构建一个自定义的AuthenticationManagerBuilder
     *
     * @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     * auth.userDetailsService((UserDetailsService) userService)// 设置UserDetailsService
     * .passwordEncoder(new BCryptPasswordEncoder());// 使用BCrypt进行密码的hash
     * }
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 添加自定义认证器
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    /**
     * 设置默认的用户名密码认证授权提供者
     * 需要配置userDetailService和passwordEncoder
     */
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        Map<String, UserDetailsService> userDetailsServiceMap = new HashMap<>();
        // 添加两种user用户类型
        userDetailsServiceMap.put(ClientEnums.ADMIN_CLIENT.getName(), sysUserDetailsService);
        userDetailsServiceMap.put(ClientEnums.MEMBER_CLIENT.getName(), memberUserDetailsService);
        provider.setUserDetailsService(new MoreAuthenticatedUserDetailsService(userDetailsServiceMap));
        provider.setPasswordEncoder(passwordEncoder());
        provider.setHideUserNotFoundExceptions(false); // 是否隐藏用户不存在异常，默认:true-隐藏；false-抛出异常；
        return provider;
    }

    /**
     * 密码加密器
     * 根据密码的前缀来决定使用哪个具体的编码器进行编码或解码
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    /**
     * 自定义未认证响应
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, e) -> {
            if (e instanceof InsufficientAuthenticationException) {
                response.addHeader("WWW-Authenticate", "Basic realm= " + realName);
                response.sendError(org.springframework.http.HttpStatus.UNAUTHORIZED.value(), org.springframework.http.HttpStatus.UNAUTHORIZED.getReasonPhrase());
            } else {
                response.setStatus(cn.hutool.http.HttpStatus.HTTP_OK);
                response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
                response.setHeader("Access-Control-Allow-Origin", "*");
                response.setHeader("Cache-Control", "no-cache");
                // 客户端认证失败
                R result = R.failed(ResultCode.CLIENT_AUTHENTICATION_FAILED);
                response.getWriter().print(JSONUtil.toJsonStr(result));
                response.getWriter().flush();
            }
        };
    }

}
